package edu.ssdut.revise.utils;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.utils.Pair;
import edu.ssdut.revise.core.match.MatchStore;
import edu.ssdut.revise.exceptions.*;
import edu.ssdut.revise.models.CUWithInfo;
import org.apache.commons.io.IOUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.RenameDetector;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;

import static edu.ssdut.revise.utils.FileUtils.isJavaFile;

public class jGitUtils {
    private static final Logger logger = LoggerFactory.getLogger(jGitUtils.class);
    private static final Integer RENAME_THRESHOLD = 55; // Rename的阈值

    /**
     * get the final MatchStore with input
     *
     * @param repoString    repo identifier
     * @param startCommitId starting commit hash
     * @param endCommitId   ending commit hash
     * @param ifLocal       if the repo passed is local repo
     * @return MatchStore or <code>null</code> if inputs are illegal
     */
    public static MatchStore<CUWithInfo> gitInput(String repoString, String startCommitId, String endCommitId, Boolean ifLocal)
            throws IOException, NotExistOrNotDirectoryException, GitOperationException, NotLegalRepositoryException, IllegalCommitHashException, ParserException {
        Repository repo;
        if (ifLocal) {
            repo = local2repo(repoString);
        } else {
            repo = remote2repo(repoString);
        }
        try {
            // check if repo is legal
            repo.getWorkTree();
        } catch (NoWorkTreeException e) {
            logger.info("illegal repository encountered: " + repo.getIdentifier());
            throw new NotLegalRepositoryException();
        }
        return repo2CUMatchStore(repo, startCommitId, endCommitId);
    }

    /**
     * open local Repository as jGit {@link Repository}
     *
     * @param localPath local path of <b>.git</b> directory
     * @return jGit {@link Repository}
     */
    public static Repository local2repo(String localPath) throws IOException, NotExistOrNotDirectoryException {
        // 注意localPath必须是.git的路径
        FileRepositoryBuilder builder = new FileRepositoryBuilder();
        File file = new File(localPath);
        if (!file.exists() || !file.isDirectory()) {
            throw new NotExistOrNotDirectoryException();
        }
        return builder.setGitDir(file)
                .readEnvironment() // scan environment GIT_* variables
                .build();
    }

    /**
     * open remote Repository as jGit {@link Repository}
     *
     * @param remoteURL remote URL of the git repository
     * @return jGit {@link Repository} and <code>null</code> if exception encountered
     */
    public static Repository remote2repo(String remoteURL) throws IOException, GitOperationException {
        // prepare a new folder for the cloned repository
        File localPath = File.createTempFile("TmpGitRepository", "");
        if (!localPath.delete()) { // 若生成的地址存在文件，直接删除
            // 若生成的临时文件存在且不可删除
            throw new IOException("Could not delete temporary file " + localPath);
        }
        try {
            Git git = Git.cloneRepository()
                    .setURI(remoteURL)
                    .setDirectory(localPath)
                    .setProgressMonitor(new SimpleProgressMonitor())
                    .call();
            return git.getRepository();
        } catch (GitAPIException e) {
            throw new GitOperationException(e.getMessage());
        }
    }

    /**
     * find matched files, transform from jGit {@link Repository} to MatchStore
     *
     * @param repo          legal git repository
     * @param startCommitId start commit hash id
     * @param endCommitId   end commit hash id
     * @throws IllegalCommitHashException illegal commit hash id
     * @throws IOException                system io exception
     * @throws GitOperationException      git operation exception
     */
    public static MatchStore<CUWithInfo> repo2CUMatchStore(Repository repo, String startCommitId, String endCommitId)
            throws IOException, GitOperationException, IllegalCommitHashException, ParserException {
        try {
            ObjectId origin = repo.resolve(startCommitId);
            ObjectId modified = repo.resolve(endCommitId);
            // if the ObjectId can not be resolved(illegal input)
            if (origin == null || modified == null) {
                if (origin == null) throw new IllegalCommitHashException(startCommitId);
                throw new IllegalCommitHashException(endCommitId);
            }
            RevWalk walk = new RevWalk(repo);
            RevTree originTree = walk.parseCommit(origin).getTree();
            RevTree modifiedTree = walk.parseCommit(modified).getTree();

            final TreeWalk tw = new TreeWalk(repo);
            tw.setRecursive(true);
            tw.addTree(originTree);
            tw.addTree(modifiedTree);
            final RenameDetector rd = new RenameDetector(repo);
            rd.setRenameScore(RENAME_THRESHOLD);  // Rename的阈值
            rd.addAll(DiffEntry.scan(tw));

            MatchStore<CUWithInfo> cuMatchStore = new MatchStore<>();
            for (DiffEntry diff : rd.compute(tw.getObjectReader(), null)) {
                // 筛选java源文件（这样先找到diff再筛选Java文件不是很高效）
                if (!isJavaFile(diff.getOldPath()) && !isJavaFile(diff.getNewPath())) {
                    //两个文件都不是.java
                    continue;
                }

                DiffEntry.ChangeType changeType = diff.getChangeType();
                // 这里就拿到了changeType，之后就可以自行处理
                if (changeType == DiffEntry.ChangeType.ADD) {
                    CompilationUnit cu = repoRelativePath2CU(repo, endCommitId, diff.getNewPath());
                    // 这里的identifier对于remote repo来说是一个临时文件地址，考虑解决方法
                    cuMatchStore.insertUnmatchedNodes2(new CUWithInfo(cu, repo.getIdentifier(), endCommitId, diff.getNewPath()));
                } else if (changeType == DiffEntry.ChangeType.DELETE) {
                    CompilationUnit cu = repoRelativePath2CU(repo, startCommitId, diff.getOldPath());
                    cuMatchStore.insertUnmatchedNodes1(new CUWithInfo(cu, repo.getIdentifier(), startCommitId, diff.getOldPath()));
                } else {
                    // copy, renamed, modified
                    CompilationUnit cu1 = repoRelativePath2CU(repo, startCommitId, diff.getOldPath());
                    CompilationUnit cu2 = repoRelativePath2CU(repo, endCommitId, diff.getNewPath());
                    if (cu1 != null && cu2 != null) {
                        CUWithInfo cuI1 = new CUWithInfo(cu1, repo.getIdentifier(), startCommitId, diff.getOldPath());
                        CUWithInfo cuI2 = new CUWithInfo(cu2, repo.getIdentifier(), endCommitId, diff.getNewPath());
                        cuMatchStore.insertMatched(new Pair<>(cuI1, cuI2));
                    }
                }
            }
            return cuMatchStore;
        } catch (CanceledException e) {
            throw new GitOperationException(e.getMessage());
        }
    }

    private static CompilationUnit repoRelativePath2CU(Repository repo, String commit, String rPath) throws IOException, ParserException {
        // rPath是getOldPath或getNewPath
        // 拿到revCommit
        ObjectId resolve = repo.resolve(commit);
        RevCommit revCommit = repo.parseCommit(resolve);

        // treeWalk拿到内容
        RevTree tree = revCommit.getTree();
        TreeWalk treeWalk = new TreeWalk(repo);
        treeWalk.addTree(tree);
        treeWalk.setRecursive(true);
        while (treeWalk.next()) {
            String pathString = treeWalk.getPathString(); // treeWalk迭代到一个文件上，拿到文件名
            if (pathString.equals(rPath)) {
                // 是当前commit对应文件
                ObjectId objectId = treeWalk.getObjectId(0);
                ObjectLoader loader = repo.open(objectId);
                StringWriter writer = new StringWriter();
                IOUtils.copy(loader.openStream(), writer);
                return javaParserUtils.string2CU(writer.toString());
            }
        }
        logger.warn("repoRelativePath2CU failed, relative Path: " + rPath);
        return null;
    }

    private static class SimpleProgressMonitor implements ProgressMonitor {
        @Override
        public void start(int totalTasks) {
            System.out.println("Starting work on " + totalTasks + " tasks");
        }

        @Override
        public void beginTask(String title, int totalWork) {
            System.out.println("-Task start-    " + title + "\t---totalWork: " + totalWork);
        }

        @Override
        public void update(int completed) {
        }

        @Override
        public void endTask() {
            System.out.println("Done");
        }

        @Override
        public boolean isCancelled() {
            return false;
        }
    }
}
